package service

import (
	"context"
	"errors"
	errs2 "github.com/zeebo/errs"
	"go.uber.org/zap"
	"onvif_server/api"
	"onvif_server/api/alarm"
	setting2 "onvif_server/internal/library/setting"
	"onvif_server/internal/model"
	"onvif_server/internal/repository"
	"onvif_server/pkg/db"
	"onvif_server/pkg/m3u8"
	"onvif_server/pkg/onvif"
	"onvif_server/pkg/subscribe"
	ws2 "onvif_server/pkg/ws"
	"time"
)

const DefaultCleanupDays = 60

type AlarmService interface {
	List(ctx context.Context, req *alarm.ListReq) (total int64, res []*alarm.DetailRes, err error)
	Create(ctx context.Context, req *alarm.CreateReq) error
	Delete(ctx context.Context, req *alarm.DeleteReq) error
	Detail(ctx context.Context, id int64) (*alarm.DetailRes, error)
	Process(ctx context.Context, req *alarm.ProcessReq) error

	GetSetting(ctx context.Context) (*alarm.Setting, error)
	SetSetting(ctx context.Context, req *alarm.Setting) error

	//WsAlarmPopup ws的弹窗消息推送
	WsAlarmPopup(cid string, msg *ws2.Msg)
	//WsAlarmPopupClose ws的弹窗消息发送关闭
	WsAlarmPopupClose(cid string, msg *ws2.Msg)
	WsAlarmNotify(cid string, msg *ws2.Msg)

	//TaskDayCleaning 清理数据day天前的数据 定时任务执行
	TaskDayCleaning(ctx context.Context) error
}

func NewAlarmService(service *Service, alarmRepo repository.AlarmRepository, cameraRepo repository.CameraRepository, subRepo repository.SubscriptionRepository, subLib *subscribe.Subscribe, wsManage *ws2.Manage) AlarmService {
	return &alarmService{
		alarmRepo:  alarmRepo,
		Service:    service,
		cameraRepo: cameraRepo,
		subRepo:    subRepo,
		subLib:     subLib,
		wsManage:   wsManage,
	}
}

type alarmService struct {
	alarmRepo  repository.AlarmRepository
	onvif      *onvif.Onvif
	m3u8       *m3u8.M3u8
	cameraRepo repository.CameraRepository
	subRepo    repository.SubscriptionRepository
	subLib     *subscribe.Subscribe
	wsManage   *ws2.Manage
	*Service
}

func (s *alarmService) List(ctx context.Context, req *alarm.ListReq) (total int64, res []*alarm.DetailRes, err error) {
	qf, err := req.ToQueryFilter()
	if err != nil {
		return
	}
	total, _res, err := s.alarmRepo.List(ctx, qf, req.Paginator.PageQuery())
	res = toDetailRes(_res)
	return
}

func (s *alarmService) Create(ctx context.Context, req *alarm.CreateReq) error {
	//todo 通过taskid 获取摄像头id
	deviceId := "testId"
	//todo 通过taskid 获取算法
	eventType := 1
	m := model.Alarm{}
	m.Image = req.Image
	m.Video = req.Video
	m.TaskId = req.TaskId
	m.Ext = []byte(req.Ext)
	m.EventType = eventType
	m.DeviceId = deviceId
	m.Status = model.AlarmStatusNormal
	err := s.alarmRepo.Create(ctx, &m)
	if err != nil {
		return err
	}
	//同步通知栏消息
	go s.syncAlarmMsg()
	//推送订阅消息
	go func() {
		err = s.subLib.Push(WithTopicToSubscribers(s.subRepo.Subscriber(context.Background()), subscribe.TopicAlarm), &subscribe.AlarmPayload{
			ID:        m.ID,
			DeviceId:  m.DeviceId,
			Image:     m.Image,
			Video:     m.Video,
			EventType: m.EventType,
			Status:    m.Status,
			Remark:    m.Remark,
			Location:  "testlocation",
			CreatedAt: m.CreatedAt,
		})
		if err != nil {
			s.log.Error("推送告警消息失败", zap.Error(err))
		}
	}()
	// 推送ws弹窗消息
	//go s.alarmWs.Send(toOneDetailRes(m))
	go s.wsManage.Groupcast(ws2.WsGroupAlarmPopup, &ws2.Msg{Action: api.WsActionAlarmPopup, Payload: toOneDetailRes(m)})
	return nil
}

func (s *alarmService) Process(ctx context.Context, req *alarm.ProcessReq) error {
	if req.Status != model.AlarmStatusFalse && req.Status != model.AlarmStatusPositive {
		return errors.New("处理结果选择错误")
	}
	defer func() {
		//同步通知栏消息
		go s.syncAlarmMsg()
	}()
	if req.ID > 0 {
		return s.alarmRepo.Update(ctx, &model.Alarm{ID: req.ID, Status: req.Status, Remark: req.Remark}, "status", "remark")
	}
	return s.alarmRepo.BatchProcessStatus(ctx, req.Status, req.Remark)
}

func (s *alarmService) Delete(ctx context.Context, req *alarm.DeleteReq) error {
	var err error
	defer func() {
		//同步通知栏消息
		go s.syncAlarmMsg()
	}()
	if len(req.IDs) > 0 {
		err = s.alarmRepo.DeleteByIDs(ctx, req.IDs)
	} else if req.Days > 0 {
		err = s.dayCleaning(ctx, req.Days)
	}
	return err
}

func (s *alarmService) dayCleaning(ctx context.Context, days int) error {
	if days <= 0 {
		return nil
	}
	tt := time.Now().Add(-time.Hour * 24 * time.Duration(days))
	dt := time.Date(tt.Year(), tt.Month(), tt.Day(), 0, 0, 0, 0, s.loc)
	return s.alarmRepo.DeleteBeforeTime(ctx, dt)
}

// TaskDayCleaning 清理数据day天前的数据
func (s *alarmService) TaskDayCleaning(ctx context.Context) (err error) {
	defer func() {
		if err == nil {
			s.log.Debug("TaskDayCleaning start")
			//同步通知栏消息
			go s.syncAlarmMsg()
		} else {
			s.log.Debug("TaskDayCleaning error", zap.Error(err))
		}
	}()
	opened := s.settingLib.GetWithDefault(setting2.AlarmOpenCleanup, false).(bool)
	if !opened {
		return nil
	}
	err = s.dayCleaning(ctx, s.settingLib.GetWithDefault(setting2.AlarmCleanupDays, DefaultCleanupDays).(int))
	return
}

func (s *alarmService) Detail(ctx context.Context, id int64) (*alarm.DetailRes, error) {
	m, err := s.alarmRepo.GetById(ctx, int(id))
	if err != nil {
		return nil, err
	}
	_loc := ""
	if m.Camera.Location != "" {
		_loc = m.Camera.Location
	}
	return &alarm.DetailRes{Alarm: m, Location: _loc}, nil
}

func (s *alarmService) GetSetting(ctx context.Context) (*alarm.Setting, error) {
	res := &alarm.Setting{}
	res.OpenCleanup, _ = s.settingLib.GetWithDefault(setting2.AlarmOpenCleanup, false).(bool)
	res.CleanupDays, _ = s.settingLib.GetWithDefault(setting2.AlarmCleanupDays, DefaultCleanupDays).(int)
	return res, nil
}

func (s *alarmService) SetSetting(ctx context.Context, req *alarm.Setting) error {
	errs := errs2.Group{}
	errs.Add(s.settingLib.Store(setting2.AlarmOpenCleanup, req.OpenCleanup),
		s.settingLib.Store(setting2.AlarmCleanupDays, req.CleanupDays),
	)
	return errs.Err()
}

func (s *alarmService) WsAlarmPopup(cid string, msg *ws2.Msg) {
	s.wsManage.AddGroup(cid, ws2.WsGroupAlarmPopup)
}

func (s *alarmService) WsAlarmPopupClose(cid string, msg *ws2.Msg) {
	s.wsManage.RemoveGroup(cid, ws2.WsGroupAlarmPopup)
}

func (s *alarmService) WsAlarmNotify(cid string, msg *ws2.Msg) {
	res, err := s.getAlarmNotify()
	if err != nil {
		s.log.Error("WsAlarmNotify error", zap.Error(err))
		return
	}
	s.wsManage.Send(cid, &ws2.Msg{Action: api.WsActionAlarmNotify, Payload: res})
}

// 同步告警通知消息通知
func (s *alarmService) syncAlarmMsg() {
	res, err := s.getAlarmNotify()
	if err != nil {
		s.log.Error("syncAlarmMsg error", zap.Error(err))
		return
	}
	if err = s.wsManage.Broadcast(&ws2.Msg{Action: api.WsActionAlarmNotify, Payload: res}); err != nil {
		s.log.Warn("syncAlarmMsg Broadcast has error", zap.Error(err))
	}
}

func (s *alarmService) getAlarmNotify() (res alarm.WsNotifyMsgRes, err error) {
	page := db.Paginator{}
	page.PageSize = 5
	ctx, cancel := context.WithTimeout(context.Background(), time.Second*30)
	defer cancel()
	total, _res, err := s.alarmRepo.List(ctx, &repository.AlarmQueryFilter{Status: model.AlarmStatusNormal}, page.PageQuery())
	if err != nil {
		return
	}
	res = alarm.WsNotifyMsgRes{
		Total: total,
		Items: toDetailRes(_res),
	}
	return
}

func toDetailRes(_res []*model.Alarm) []*alarm.DetailRes {
	res := make([]*alarm.DetailRes, len(_res))
	if len(_res) > 0 {
		for i := range _res {
			res[i] = toOneDetailRes(*_res[i])
		}
	}
	return res
}

func toOneDetailRes(res model.Alarm) *alarm.DetailRes {
	_loc := ""
	if res.Camera.Location != "" {
		_loc = res.Camera.Location
	}
	return &alarm.DetailRes{
		Alarm:    &res,
		Location: _loc,
	}
}
